package evm

import (
	"strconv"

	"github.com/golang/protobuf/proto"
	ctypes "hundsun.com/hsl/hschain/common/types"
	"hundsun.com/hsl/hschain/common/util/pb"
	"hundsun.com/hsl/hschain/contract/evm/ext"
	"hundsun.com/hsl/hschain/contract/evm/state"
	"hundsun.com/hsl/hschain/protos/ledger"
	"hundsun.com/hsl/hschain/store/extend/account"

	"hundsun.com/hsl/hschain/common/config"
	"hundsun.com/hsl/hschain/common/log"
	"hundsun.com/hsl/hschain/contract"
	evmcom "hundsun.com/hsl/hschain/contract/evm/ext/common"
	"hundsun.com/hsl/hschain/contract/evm/types"
	"hundsun.com/hsl/hschain/contract/evm/vm"
	ecom "hundsun.com/hsl/hschain/executor/common"
	pbcom "hundsun.com/hsl/hschain/protos/common"
	pbevm "hundsun.com/hsl/hschain/protos/evm"
	"hundsun.com/hsl/hschain/protos/execute"
)

// Reg 注册执行器名称
func Reg(cfg *config.PluginConfig) {
	height := uint64(0)
	if cfg != nil {
		if v, ok := cfg.Parameters["height"]; ok {
			h, err := strconv.ParseInt(v, 10, 64)
			if err == nil && h > 0 {
				height = uint64(h)
			}
		}
	}
	contract.Register(types.EVM, newEvm, false, false, height)
}

// Evm ...
type Evm struct{}

func newEvm() contract.Contract {
	return &Evm{}
}

// Init 初始化
func (e *Evm) Init() {}

// Run 运行
func (e *Evm) Run(ctx *ecom.ExecContent, gtx *execute.GroupedTransaction) (*execute.GroupedTransaction, error) {
	var err error
	gtx, _, err = exec(ctx, gtx, false)
	return gtx, err
}

func exec(ctx *ecom.ExecContent, gtx *execute.GroupedTransaction, readOnly bool) (*execute.GroupedTransaction, uint64, error) {
	tx := gtx.GetTx()
	// contract create
	isCreate := tx.GetTo() == ""
	var (
		ret          []byte
		contractAddr evmcom.Address
		leftGas      uint64
		usedGas      uint64
		err          error
	)

	// 生成evm执行消息
	msg, err := GenMessage(tx)
	if err != nil {
		log.Errorf("evm run GenMessage fail %v", err)
		gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_FAILED_OTHER_REASON
		if err == ctypes.ErrInvalidParam {
			gtx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_PAYLOAD_EMPTY
		}
		return gtx, usedGas, err
	}

	if !readOnly {
		// 扣除交易费
		kv, err := ProcessFee(ctx, tx)
		if err != nil {
			log.Errorf("evm run ProcessFee fail %v", err)
			gtx.TxExecRst.Receipt.TxStatus = MatchTxStatusAndEvmError(err)
			return gtx, usedGas, err
		}
		gtx.TxExecRst.Kvs = append(gtx.TxExecRst.Kvs, kv)
	}

	// 生成evm执行环境
	chainCfg := NewDefaultChainConfig()
	cfg := NewEvmConfig(false, log.IsTraceEnabled())
	statedb := state.NewEVMStateDB(ctx, readOnly)
	txHash := gtx.GetTxExecRst().GetReceipt().GetTxHash()
	statedb.SetTxHash(evmcom.BytesToHash(txHash))
	blockContext := NewEVMBlockContext(msg, ctx.GetBlockHeader())
	txContent := NewEVMTxContext(*msg)
	env := vm.NewEVM(blockContext, txContent, statedb, chainCfg, *cfg)
	if isCreate { // TODO 后续这里有需要考虑jvm和evm合约地址生成区分,因此需要修改Create函数
		data := msg.Code()
		if len(msg.Param()) > 0 {
			data = append(data, msg.Param()...)
		}
		ret, contractAddr, leftGas, err = env.Create(vm.AccountRef(msg.From()), data, msg.GasLimit(), msg.Value())
	} else {
		contractAddr = *msg.To()
		if statedb.Exist(contractAddr) {
			ret, leftGas, err = env.Call(vm.AccountRef(msg.From()), *msg.To(), msg.Param(), msg.GasLimit(), msg.Value())
		} else {
			leftGas = msg.GasLimit()
			err = types.ErrContractNotExsit
		}
	}
	usedGas = msg.GasLimit() - leftGas
	// 交易回执等更新
	gtx.TxExecRst.Receipt.Events = statedb.GetContractEvents()
	gtx.TxExecRst.Receipt.CallResult = ret
	gtx.TxExecRst.Receipt.ContractAddress = contractAddr.ToNoPrefixHex()
	// 非readOnly需要标记
	if !readOnly {
		gtx.VarsRWStatus = append(gtx.VarsRWStatus, statedb.GetReadWriteSign()...) // 更新状态数据操作情况, 用于冲突检查
		if err == nil {                                                            //执行正确情况下，更新交易执行后修改的状态数据
			gtx.TxExecRst.Kvs = append(gtx.GetTxExecRst().Kvs, statedb.GetContractKV()...)
		}
	}
	// 执行结果检查
	if err != nil {
		log.Errorf("evm run error address is %x, gas left %d, used %d, ret len %d, error %v", contractAddr, leftGas, usedGas, len(ret), err)
		gtx.TxExecRst.Receipt.TxStatus = MatchTxStatusAndEvmError(err)
		return gtx, usedGas, err
	}
	log.Debugf("evm run success, address is %x, gas left %d, used %d, ret len %d", contractAddr, leftGas, usedGas, len(ret))
	// update 临时缓存中,用于连续执行交易
	for _, kv := range gtx.GetTxExecRst().Kvs {
		ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
	}
	return gtx, usedGas, err
}

// GenMessage Transaction => Message
func GenMessage(tx *ledger.Transaction) (msg *ext.Message, err error) {
	value := tx.GetPayload()
	if len(value) == 0 {
		return nil, ctypes.ErrInvalidParam
	}
	var action pbevm.EVMContractAction
	err = proto.Unmarshal(value, &action)
	if err != nil {
		return nil, err
	}
	gasPrice := action.GetGasPrice()
	if gasPrice == 0 {
		gasPrice = 1
	}
	// 先将tx.Fee扣除，再根据gasPrice计算出gasLimit，这里EVM的fee不做扣除后的偿还
	gasLimit := tx.GetFee() / uint64(gasPrice)
	if gasLimit == 0 {
		gasLimit = pb.GetGenesisTransactionPayload().GetGasLimit()
	}
	from := evmcom.HexToAddress(tx.GetFrom())
	var to evmcom.Address
	if tx.GetTo() != "" {
		to = evmcom.HexToAddress(tx.GetTo())
	}
	// 合约的GasLimit即为调用者为本次合约调用准备支付的手续费
	msg = ext.NewMessage(from, &to, int64(tx.Nonce), tx.Value, gasLimit, gasPrice, action.GetCode(), action.GetParam(), tx.GetContractId())
	return msg, nil
}

// Rollback 回滚
func (e *Evm) Rollback(ctx *ecom.ExecContent, tx *execute.GroupedTransaction) (*execute.GroupedTransaction, error) {
	return nil, nil
}

// MatchTxStatusAndEvmError err and txstatus match
func MatchTxStatusAndEvmError(err error) ledger.TransactionReceipt_TxStatus {
	switch err {
	case nil:
		return ledger.TransactionReceipt_SUCCESSFUL
	case vm.ErrOutOfGas, vm.ErrCodeStoreOutOfGas:
		return ledger.TransactionReceipt_OUT_OF_GAS
	case vm.ErrDepth:
		return ledger.TransactionReceipt_CONTRACT_OUT_DEPTH
	case vm.ErrInsufficientBalance:
		return ledger.TransactionReceipt_INSUFFICIENT_FUNDS
	case vm.ErrContractAddressCollision:
		return ledger.TransactionReceipt_CREATE_CONTRACT_ADDRESS_ERROR
	case vm.ErrExecutionReverted:
		return ledger.TransactionReceipt_CALL_CONTRACT_ERROR
	case vm.ErrMaxCodeSizeExceeded, vm.ErrInvalidCode:
		return ledger.TransactionReceipt_CONTRACT_CODE_CHECK_INVALID
	case vm.ErrInvalidJump, vm.ErrWriteProtection, vm.ErrReturnDataOutOfBounds, vm.ErrGasUintOverflow:
		return ledger.TransactionReceipt_FAILED_OTHER_REASON
	case types.ErrContractNotExsit:
		return ledger.TransactionReceipt_CONTRACT_ACCOUNT_NOT_EXSIT
	default:
		return ledger.TransactionReceipt_FAILED_OTHER_REASON
	}
}

// ProcessFee 交易费处理
func ProcessFee(ctx *ecom.ExecContent, tx *ledger.Transaction) (*pbcom.KeyValue, error) {
	if tx.GetFee() == 0 { // 如果交易费为0情况下不去处理，说明已经通过了最开始的系统交易费检查
		return nil, nil
	}
	from := tx.GetFrom()
	acc := account.NewAccountDB(ctx)
	fromAccount, err := acc.GetAccount(from)
	if err != nil {
		return nil, err
	}
	if fromAccount.GetBalance() < tx.GetFee() {
		return nil, vm.ErrInsufficientBalance
	}
	fromAccount.Balance -= tx.GetFee()
	kv, _ := account.KV(fromAccount)
	ctx.GetStateHandle().SetLocal(kv.Key, kv.Value)
	return kv, nil
}
